#include "languageparser.h"
#include "numrule.h"

#include <fstream>
#include <iostream>
#include "calc.h"

using namespace std;

/** Static member fields. */
const string LanguageParser::SECTION_NUMBERS = "[NUMBERS]";
const string LanguageParser::SECTION_OPERATORS = "[OPERATORS]";

const string LanguageParser::SUFFIX = ".parser";

/** Constructor using file path. 
 * It is prepared for parsing directories, in spite of its actual invocation 
 * by Calc (file name only, since it parses the dir '.' anyway)
 */
LanguageParser::LanguageParser(string path) {
  size_t pos = path.find_last_of('/');
  int len;
  if (pos==string::npos) {
    // actually, pos==-1 here, and (pos+1)==0, but since size_t is unsigned,
    // it is ugly to say something like that here (even though it works).
    len = path.length() - SUFFIX.length();
    name = path.substr(0, len);
  } else {
    len = path.length() - pos - 1 - SUFFIX.length();
    name = path.substr(pos+1, len);
  }

  // This is an ad-hoc assumption that nobody will define literals with 10^8 
  // or more characters. Sounds reasonable, but could use string's max size.
  num_minlen = 99999999;
  num_maxlen = 0;

  op_minlen = 99999999;
  op_maxlen = 0;

  ifstream ifs(path.c_str());
  string section = "";
  string s("");
  while (getline(ifs,s)) {
    // Comments are skipped
    size_t pos = string::npos;
    if (s[0]=='#') // comment
      continue;

    // Sections are noted and remembered
    if (s==SECTION_NUMBERS) {
      section = s;
      continue;
    }
    if (s==SECTION_OPERATORS) {
      section = s;
      continue;
    }

    // Whitespace lines skipped
    if ((pos = s.find_first_not_of(" \t")) == string::npos) // not a rule
      continue;

    // Records found are split into key and value at the first colon. Therefore,
    // colon should not appear in STATEs.
    // Otherwise, the rule is NOT checked for syntax (e.g. too few or too many 
    // fields)
    pos = s.find(':');
    string key = s.substr(0, pos);
    string value = s.substr(pos+1);

    if (section==SECTION_NUMBERS) {
      // Numbers are processed by a rule class. Still no check for syntax or 
      // semantics.
      pair<const string, NumRule> p(key, NumRule(key, value));
      num_rules.insert(p);
      // The literals' min and max length is adjusted to the newly found 
      // literal.
      string literal = key.substr(key.find(",")+1);
      size_t litlen = literal.length();
      if (num_minlen > litlen) num_minlen = litlen;
      if (num_maxlen < litlen) num_maxlen = litlen;
    } else if (section==SECTION_OPERATORS) {
      // Operators are parsed more simply. Only the first character matters,
      // and it is NOT checked against allowed operators.
      pair<const string, char> p(key, value[0]);
      op_rules.insert(p);
      // The literals' min and max length is adjusted to the newly found 
      // literal.
      size_t litlen = key.substr(key.find(":")+1).length();
      if (op_minlen > litlen) op_minlen = litlen;
      if (op_maxlen < litlen) op_maxlen = litlen;
    }
  }
}

/** Parse a number.
 * This is a sensitive parsing; it ends either at the end of string, or at the
 * first operator recognised by *ANY* of the parsers.
 */
Number *LanguageParser::parseNum(string input, size_t &pos) const {
  // The good old state machine...
  string state = "S";
  // ... and the value we plan to return.
  Number *n = new Number(); 

  // Since we do not know the results of parsing as of yet, the position is
  // saved and updated only if a proper match found.
  size_t mypos = pos;
  while (mypos != input.length()) {
    // ignore whitespace coming
    while(Parser::WHITESPACE.find(input[mypos])!=string::npos) mypos++;

    // check for number: from largest to smallest

    // if number is already parsed for something, an operator should have 
    // precedence over a number. This is necessary because it's not possible
    // to distinguish the sign 'minus' from the arithmetic operator 'minus'.
    if (!n->isFirst()) {
      size_t op_pos = mypos;
      // doing a small detour to central parser... 
      // Quick and dirty. could be made with calc passed to parser... but we
      // do not want to mess up our invoker's state.
      Calc *calc = new Calc(input);
      char op = calc->parseOp(input, op_pos);
      delete calc;
      if (op!=NOTANOPERATOR) {
	// operator found, finish parsing
	pos = mypos;
	return n;
      }
    }

    size_t remaining = input.length() - mypos;
    // whitespace may have run out...
    if (!remaining) {
      continue;
    }
    size_t maxlen;

    bool found = false;

    // Otherwise, keep parsing the number.
    maxlen = (num_maxlen > remaining ? remaining : num_maxlen);
    for (size_t l = maxlen; l >= num_minlen; l--) {
      // Rules are matched either by STATE,literal or *,literal (the first form
      // having precedence over the global one)
      string key(state + "," + input.substr(mypos,l));
      NumRule const *rule = NULL;
      map<string, NumRule>::const_iterator p = num_rules.find(key);
      if (p!=num_rules.end()) {
	rule = &(p->second);
      } else { // try global rule
	key = "*," + input.substr(mypos,l);
	p = num_rules.find(key);
	if (p!=num_rules.end()) {
	  rule = &(p->second);
	}
      }

      // If rule is found either way, apply it.
      if (rule != NULL) {
	string newstate = rule->apply(*n);
	// Set new state, unless the special "*" used, or no state returned
	if (newstate != "*" && newstate!="") state = newstate;

	found = true;
	mypos += l;
	break;
      }
    }

    // If we get here, it means that no rule was found to match any suitable
    // prefix of the input. No number should be returned and position should
    // not be updated.
    if (!found) {
      // We return a canned response, destroy the allocated number.
      delete n;
      return NOTANUMBER;
    }
  }

  // Update position and return calculated number. Actually, this is the last
  // location where we need a Number. Everywhere else a plain long int would 
  // do. (another refactoring opportunity)
  pos = mypos;
  return n;
}

/** Parse an operator.
 * Quite straightforward, even if there are multiple names of the same thing.
 * One weakness is that it is sensitive to leading whitespace.
 */
char LanguageParser::parseOp(string input, size_t &pos) const {
  size_t remaining = input.length() - pos;
  size_t maxlen = (op_maxlen > remaining ? remaining : op_maxlen);

  // Iterate over prefixes to find a known literal
  for (size_t l = maxlen; l >= op_minlen; l--) {
    string key = input.substr(pos,l);
    char c = NOTANOPERATOR;
    map<string, char>::const_iterator p = op_rules.find(key);
    if (p!=op_rules.end()) {
      c = p->second;
      pos += l;
      return c;
    }
  }

  return NOTANOPERATOR;
}
